#include "stdio.h"
#include "sys/types.h"
#include "netdb.h"
#include "sys/socket.h"
#include "stdlib.h"
#include "string.h"
#include "Cache.h"
#include <iostream>
#include <cassert>

Cache* Cache::mCache = NULL;

bool Cache::ExistsAndValid(std::string& uri)
{
	//if the uri doesn't exist in the map, return false
	if(mCacheMap.find(uri) == mCacheMap.end())
		return false;

	//if the page is not expired, return true
	if(mCacheMap[uri].mExpires == false) // page never expires
	{
		std::cout<<"Cache works: Page Never Expires"<<std::endl;
		return true;
	}
	if(mCacheMap[uri].mExpireTime >= time(NULL)) // not expired yet
	{
		std::cout<<"Cache works: Page has not expired yet."<<std::endl;
		return true;
	}
	//TODO: BONUS (use HEAD to see Last-Modified and compare ExpireStr)
	time_t last_modified_time = lastmodified_time(uri);
	if (last_modified_time == 0)
	{
		std::cout<<"no last modified time on page!"<<std::endl;
		return false;
	}
	bool NotModified = ((mCacheMap[uri].mLastFetchedTime)>last_modified_time);
	if (NotModified)
	{
		std::cout<<"Cache works: BONUS works!!!!"<<std::endl;
	}
	return NotModified;
	//if the origin server's page is not updated since, return true
	//else return false

	return false;
}

time_t Cache::lastmodified_time(std::string& uri)
{
	size_t endpos = uri.find_first_of("/",7);
	std::string hostname;
	if(endpos == std::string::npos)
	{
		hostname = std::string(uri, 7);
	}
	else
	{
		hostname = std::string(uri,7, endpos-7);
	}
	std::cout<<"FOR HEAD--Origin Server: "<< hostname.c_str() << std::endl;
	
	static int status;
	static struct addrinfo hints;
	static struct addrinfo *res;

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	
	if((status = getaddrinfo(hostname.c_str(), "80", &hints, &res))!= 0)
	{
		std::cout << "FOR HEAD -- getaddrinfo() error: "<< gai_strerror(status) << std::endl;
		return -1;
	}
		
	int tempFD = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
	if(tempFD == -1)
	{
		perror("FOR HEAD -- socket() error");
		return -1;
	}
	if(connect(tempFD, res->ai_addr, res->ai_addrlen) == -1)
	{
		perror("connect() error: ");
		close(tempFD);
	}
	std::cout<<"FOR HEAD -- connected successfully to Server!!"<<std::endl;
	freeaddrinfo(res);
	
	char SendBuf[MSG_BUF_SIZE];
	sprintf(SendBuf, "HEAD %s HTTP/1.0%s", uri.c_str(), DOUBLE_CRLF);
	
	std::cout << "Sending HTTP HEAD massage: "<< std::endl << SendBuf << std::endl;
	send(tempFD, SendBuf, strlen(SendBuf), 0);
	
	mMsgBufPtr = 0;
	fd_set readFDS;
	FD_ZERO(&readFDS);
	FD_SET(tempFD, &readFDS);
	int maxfd = tempFD;
	struct timeval tv;
	char tempMsgBuf[MSG_BUF_SIZE];
	tv.tv_sec = 5; tv.tv_usec = 0;
	int retVal;
	bool HeaderReceived = false;
	while(HeaderReceived == false)
	{
		retVal = select(maxfd+1, &readFDS, NULL, NULL, &tv);
		if(retVal == -1)
		{
			perror("FOR HEAD -- select() error: ");
			return false;
		}
		else if(retVal == 0)
		{
			std::cout<< "FOR HEAD, timeout occured."<<std::endl;
			return false;
		}
		if(FD_ISSET(tempFD, &readFDS))
		{
			int bytecount = recv(tempFD, tempMsgBuf, MSG_BUF_SIZE, 0);
			if (bytecount == -1)
			{
				perror("FOR HEAD -- recv() error:");	
			}
			else if(bytecount == 0)
			{
				std::cout<< "Server closed the socket!"<< std::endl;
			}
			else
			{
				unsigned int bytestoeat = bytecount;
				size_t pos;
				
				std::string temp(tempMsgBuf, bytestoeat);
				if((pos = temp.find(DOUBLE_CRLF)) == std::string::npos)
				{
					//not yet
				}
				else
				{
					//Header is complete
					HeaderReceived = true;
				}
				memcpy(mMsgBuf + mMsgBufPtr, tempMsgBuf, bytestoeat);
				mMsgBufPtr += bytestoeat;
				

			}
		}
	}
	std::string header(mMsgBuf, mMsgBufPtr);
	size_t Last_ModifiedPos = header.find(LASTMODIFIED);
	if (Last_ModifiedPos == std::string::npos)
	{
		return 0;
	}
	int Last_ModifiedEndPos = header.find(CRLF, Last_ModifiedPos);
	std::string last_modified_time_string(header, Last_ModifiedPos + strlen(LASTMODIFIED), Last_ModifiedEndPos - Last_ModifiedPos - strlen(LASTMODIFIED));
	std::cout<<"last modified time is: "<<last_modified_time_string<<std::endl;
	time_t last_modified_time = ConvertToTime(last_modified_time_string);
	return last_modified_time;		
}

std::string& Cache::GetPage(std::string& uri)
{
	mCacheMap[uri].mLastFetchedTime = time(NULL);
	return mCacheMap[uri].mPage;
}

int Cache::SetPage(const std::string& uri, const std::string& page)
{
	mCacheMap[uri]; //this inserts a dummy if one doesn't exist

	mCacheMap[uri].mPage = page;
	time_t currentTime = time(NULL);
	mCacheMap[uri].mLastFetchedTime = currentTime;

	size_t expiresPos = page.find(EXPIRES);
	if(expiresPos == std::string::npos) // no Expires header
		mCacheMap[uri].mExpires = false;
	else
	{
		size_t headerEndPos = page.find(DOUBLE_CRLF);
		std::string header(page, 0, headerEndPos);
		std::cout<<"header is :"<<std::endl<<header<<std::endl;
		std::cout<<"header is above"<<std::endl;
		if(expiresPos > headerEndPos) // that string isn't a header
			mCacheMap[uri].mExpires = false;
		else
		{
			mCacheMap[uri].mExpires = true;
			int expiresEndPos = page.find(CRLF, expiresPos);
			mCacheMap[uri].mExpireStr = std::string(page,
				expiresPos + strlen(EXPIRES),
				expiresEndPos - expiresPos - strlen(EXPIRES));
			std::cout<<"Expires time is: "<<mCacheMap[uri].mExpireStr<<std::endl;
			mCacheMap[uri].mExpireTime = ConvertToTime(mCacheMap[uri].mExpireStr);
			
		}
	}
	
	// now do the LRU
	if(mCacheMap.size() > MAX_CACHE_SIZE) //can only be 1 larger
	{
		for(UriToPageMapping::iterator tempIter = mCacheMap.begin();
			tempIter != mCacheMap.end();
			++tempIter)
		{
			if(tempIter->second.mExpires)
			{
				if(tempIter->second.mExpireTime < currentTime)
				{
					//time exceeded, delete and return
					std::cout << "Kicking out: " << tempIter->first.c_str() << std::endl;
					mCacheMap.erase(tempIter);
					return 0;
				}
			}
		}

		// none of them has expired, do LRU
		time_t oldestFetchedTime = currentTime;
		UriToPageMapping::iterator oldestPageIter = mCacheMap.end();
		for(UriToPageMapping::iterator tempIter = mCacheMap.begin();
			tempIter != mCacheMap.end();
			++tempIter)
		{
			if(tempIter->second.mLastFetchedTime < oldestFetchedTime)
			{
				oldestFetchedTime = tempIter->second.mLastFetchedTime;
				oldestPageIter = tempIter;
			}
		}
		if(oldestPageIter != mCacheMap.end()) // should never be
		{
					std::cout << "Kicking out: " << oldestPageIter->first.c_str() << std::endl;
			mCacheMap.erase(oldestPageIter);
			return 0;
		}
		return -1;
	}
	return 0;
}

time_t Cache::ConvertToTime(const std::string& timeStr)
{
	std::cout << "Converting to time: " << timeStr.c_str() << std::endl;

	if(timeStr.c_str()=="-1")
	return 0;	

	size_t commaPos = timeStr.find_first_of(',');

	time_t temp;
	struct tm * timeinfo;
	time(&temp);
	timeinfo = localtime(&temp);

	if(commaPos == 3) // rfc 1123
	{
		timeinfo->tm_mday = atoi(std::string(timeStr, 5, 2).data());
		switch(timeStr[8])
		{
			case 'J':
				if(timeStr[9] == 'a')
					timeinfo->tm_mon = 0; // Jan
				else
				{
					if(timeStr[10] == 'l')
						timeinfo->tm_mon = 6; // Jul
					else
						timeinfo->tm_mon = 5; // Jun
				}
			break;

			case 'F':
				timeinfo->tm_mon = 1; // Feb
			break;
			
			case 'M':
				if(timeStr[10] == 'r')
					timeinfo->tm_mon = 2; // Mar
				else
					timeinfo->tm_mon = 4; // May
			break;

			case 'A':
				if(timeStr[9] == 'p')
					timeinfo->tm_mon = 3; // Apr
				else
					timeinfo->tm_mon = 7; // Aug
			break;

			case 'S':
				timeinfo->tm_mon = 8; // Sep
			break;

			case 'O':
				timeinfo->tm_mon = 9; // Oct
			break;
			
			case 'N':
				timeinfo->tm_mon = 10; // Nov
			break;

			case 'D':
				timeinfo->tm_mon = 11; // Dec
			break;

			default:
				assert(false);
			break;
		}
		timeinfo->tm_year = atoi(std::string(timeStr, 12, 4).data()) 
								- 1990;

		timeinfo->tm_hour = atoi(std::string(timeStr, 17, 2).data());
		timeinfo->tm_min = atoi(std::string(timeStr, 20, 2).data());
		timeinfo->tm_sec = atoi(std::string(timeStr, 23, 2).data());

		return mktime(timeinfo);
	}
	else
	{
		return -1;
	}
}










